﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Graph2
{
    class Axes
    {
        private float m_Ymin, m_Ymax, m_Xmin, m_Xmax;
		private int m_ScreenWidth, m_ScreenHeight;

		private float XScaleToScreen
		{
			get
			{
				return m_ScreenWidth / (m_Xmax - m_Xmin);
			}
		}

		private float YScaleToScreen
		{
			get
			{
				return m_ScreenHeight / (m_Ymax - m_Ymin);
			}
		}

		private PointF ToScreen(float x, float y)
		{
			return new PointF((x + m_Translate.X - m_Xmin) * XScaleToScreen, m_ScreenHeight - (y + m_Translate.Y - m_Ymin) * YScaleToScreen);
		}

		public PointF ToAxes(int x, int y)
		{
			return new PointF(x / XScaleToScreen - m_Translate.X + m_Xmin, (m_ScreenHeight - y) / YScaleToScreen - m_Translate.Y + m_Ymin);
		}

		private PointF m_Translate, m_StartTranslate;
		private PointF m_Origin;

		private const int TICK_DIST = 40;
		private const int GUARD_BAND = 4;

		private Color m_ClearColor = Color.White;
		private Color m_AxesColor = Color.Blue;
		private int m_AxesWidth = 2;
		private int m_ArrowTipSize = 12;
		private int m_TickSize = 12;
		private Font m_AxesFont;
		private int m_FontSize = 8;

        public Axes()
        {
			ResetZoom();
			m_AxesFont = new Font(FontFamily.GenericSansSerif, m_FontSize);
			m_Origin = new PointF(0, 0);
        }

		public void SetScreenSize(int width, int height)
		{
			m_ScreenWidth = width;
			m_ScreenHeight = height;
		}

		public void StartTranslate()
		{
			m_StartTranslate = m_Translate;
		}

		public void Translate(int deltaX, int deltaY)
		{
			m_Translate.X = m_StartTranslate.X + deltaX / XScaleToScreen;
			m_Translate.Y = m_StartTranslate.Y + deltaY / YScaleToScreen;
		}

		public void Zoom(Point start, Point end)
		{
			PointF s = ToAxes(start.X, start.Y);
			PointF e = ToAxes(end.X, end.Y);

			m_Xmin = Math.Min(s.X, e.X);
			m_Xmax = Math.Max(s.X, e.X);
			m_Ymin = Math.Min(s.Y, e.Y);
			m_Ymax = Math.Max(s.Y, e.Y);

			m_Translate.X = 0; // -(m_Xmax + m_Xmin) / 2;
			m_Translate.Y = 0; // -(m_Ymax - m_Ymin) / 2;

		}

		public void ResetZoom()
		{
			m_Xmin = -10;
			m_Xmax = 10;
			m_Ymin = -10;
			m_Ymax = 10;

			m_Translate = new PointF(0, 0);
		}

		private float AdjustUnit(float Number)
		{
			float Exponent = (float)Math.Floor(Math.Log10(Number));
			float Factor = (float)Math.Pow(10, Exponent);

			Number /= Factor;
			
			if(Number > 5)
				Number = 10;
			else if(Number > 2)
				Number = 5;
			else
				Number = 2;

			return Number * Factor;
		}

        public void Draw(Graphics g)
        {
			Pen axesPen = new Pen(m_AxesColor, m_AxesWidth);
			Pen tickPen = new Pen(m_AxesColor, 1);

			PointF[] arrowTipRight = {				
				new PointF(-m_ArrowTipSize/2 / XScaleToScreen, -m_ArrowTipSize / 2 / YScaleToScreen),
				new PointF(0, 0),
				new PointF(-m_ArrowTipSize/2 / XScaleToScreen, m_ArrowTipSize / 2 / YScaleToScreen)
			};

			arrowTipRight = arrowTipRight.Select(p => ToScreen(p.X + m_Xmax - m_Translate.X, p.Y)).ToArray();

			PointF[] arrowTipTop = {
				new PointF(-m_ArrowTipSize/2 / XScaleToScreen, m_ArrowTipSize / 2 / YScaleToScreen),
				new PointF(0, 0),
				new PointF( m_ArrowTipSize/2 / XScaleToScreen, m_ArrowTipSize / 2 / YScaleToScreen)
			};

			arrowTipTop = arrowTipTop.Select(p => ToScreen(p.X, m_Ymax - m_Translate.Y - p.Y)).ToArray();

			g.Clear(m_ClearColor);
			g.DrawLine(axesPen, ToScreen(m_Xmin - m_Translate.X, m_Origin.Y), ToScreen(m_Xmax - m_Translate.X, m_Origin.Y));
			g.DrawLine(axesPen, ToScreen(m_Origin.X, m_Ymin - m_Translate.Y), ToScreen(m_Origin.X, m_Ymax - m_Translate.Y));
			g.FillPolygon(axesPen.Brush, arrowTipRight);
			g.FillPolygon(axesPen.Brush, arrowTipTop);

			int ticksX = m_ScreenWidth / TICK_DIST;
			float dstX = (m_Xmax - m_Xmin) / ticksX;

			float tickSizeX = AdjustUnit(dstX);

			PointF maximum = ToAxes(m_ScreenWidth, 0);
			PointF minimum = ToAxes(0, m_ScreenHeight);

			float startX = (float)Math.Floor(minimum.X / tickSizeX) * tickSizeX;
			for (float t = m_Origin.X + startX; t < m_Origin.X + maximum.X; t += tickSizeX)
			{
				if (Math.Abs(t) < float.Epsilon) continue;

				PointF pos = ToScreen(t, -(m_TickSize / 2) / YScaleToScreen);
				String label = t.ToString("0.##");			
				SizeF labelSize = g.MeasureString(label, m_AxesFont);

				if (pos.X < GUARD_BAND) continue;
				if (pos.X > m_ScreenWidth - GUARD_BAND) continue;

				g.DrawLine(tickPen, pos.X, pos.Y, pos.X, pos.Y - m_TickSize);
				g.DrawString(label, m_AxesFont, Brushes.Black, pos.X - labelSize.Width / 2, pos.Y);
			}

			int ticksY = m_ScreenHeight / TICK_DIST;
			float dstY = (m_Ymax - m_Ymin) / ticksY;

			float tickSizeY = AdjustUnit(dstY);

			float startY = (float)Math.Floor(minimum.Y / tickSizeY) * tickSizeY;
			for (float t = m_Origin.Y + startY; t < m_Origin.Y + maximum.Y; t += tickSizeY)
			{
				if (Math.Abs(t) < float.Epsilon) continue;

				PointF pos = ToScreen(-(m_TickSize / 2) / XScaleToScreen, t);
				String label =  t.ToString("0.##");
				SizeF labelSize = g.MeasureString(label, m_AxesFont);

				if (pos.Y < GUARD_BAND) continue;
				if (pos.Y > m_ScreenHeight - GUARD_BAND) continue;

				g.DrawLine(tickPen, pos.X, pos.Y, pos.X + m_TickSize, pos.Y);
				g.DrawString(label, m_AxesFont, Brushes.Black, pos.X - labelSize.Width, pos.Y - labelSize.Height / 2);
			}
		}
	}
}
